package hivMultiCompartment;

import java.awt.Point;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;

import uchicago.src.sim.space.Diffuse2D;
import uchicago.src.sim.space.Object2DGrid;
import uchicago.src.sim.util.SimUtilities;

public class Organ2DSpace extends OrganGeneral {

	//layers of grid
	public SemiTorus cellSpace;
	public SmallParticleGrid virusSpace;
	public Diffuse2D antibodySpace;
	
	//value space used for display virus and antibody load
	public Object2DGrid virusAntibodyMap;

	public ArrayList<FixedObject> inputPorts;
	public ArrayList<FixedObject> allPorts; // for graphics
	
	//acutual locations to place input cells
	public final HashSet<Point> inputLocationTCell;
	public final HashSet<Point> inputLocationBCell;
	public final HashSet<Point> inputLocationAPCell;
	public final ArrayList<Point> inputLocationVirus;
	
	private static int virusDisplayUpperLimit = 20;
	private static int AntibodyDisplayUpperLimit = 50;
	
	//parameters
	private int xSize, ySize;
	
	/**
	 * constructor
	 * build cell space, virus space and antibody space;
	 * initiate cell list, virus list, port list and input queues
	 * @param sizeX
	 * @param sizeY
	 * @param aModel
	 * @param diffusionK
	 * @param degenerateRate
	 */
	public Organ2DSpace (int sizeX, int sizeY, Model aModel, double diffusionK, double degenerateRate) {
		super(aModel);
		xSize = sizeX;
		ySize = sizeY;
		cellSpace = new SemiTorus(sizeX, sizeY, aModel, this);
		virusSpace = new SmallParticleGrid(sizeX, sizeY, aModel, this);
		antibodySpace = new Diffuse2D(diffusionK, degenerateRate,sizeX, sizeY);
		virusAntibodyMap = new Object2DGrid(sizeX, sizeY);
		inputPorts = new ArrayList<FixedObject> ();
		allPorts = new ArrayList<FixedObject> ();
		inputLocationTCell = new HashSet<Point>();
		inputLocationBCell = new HashSet<Point>();
		inputLocationAPCell = new HashSet<Point>();
		inputLocationVirus = new ArrayList<Point>();
		
		updateVirusAntibodyMap();
	}
	
	//getter/setters
	public int getSizeX () {
		return xSize;
	}
	public int getSizeY () {
		return ySize;
	}
	
	//
	
	/**
	 * activities for the organ in each time step
	 */
	public void step(){
		
		antibodySpace.diffuse();
		
		recruitAgents();
		//System.out.println("CellList length: " + cellList.size());
		
		//shuffle 
		shuffleAgents();
		// Iterate through cell list
		Iterator<Cell> cIter = cellList.iterator();
		while (cIter.hasNext()) {
			Cell c = cIter.next();
//			if(APCell.class.isInstance(c) && ((APCell)c).state ==AgentState.MATURE){
//				System.out.println(c.presentStrain);
//			}
			boolean toExit = c.step();
			if(toExit) {
				cIter.remove();
			}
		}
		cellList.addAll(prolifCellList);
		prolifCellList.clear();
		
		//virus move
		Iterator<Virus> vIter = virusList.iterator();
		while (vIter.hasNext()) {
			Virus v = vIter.next();
			boolean toRemove = v.step();
			
			if(toRemove) {
				vIter.remove();
				//System.out.printf("Exit event! virus left: %d; Blood input queue size: %d\n", virusList.size(), theModel.bloodStream.inputVirusList.size());
			}
		}
		
		// Interactions between agents
		//cells interact
		cIter = cellList.iterator();
		while (cIter.hasNext()) {
			Cell c = cIter.next();
			if (c.isAlive()){
				c.statesAndInteractions();
			}
		}
		//virus interact
		vIter = virusList.iterator();
		while (vIter.hasNext()) {
			Virus v = vIter.next();
			if (v.isAlive()){
				v.statesAndInteractions();
			}
		}
		
		
		removeDeadAgents();
		
		antibodySpace.update();
		updateVirusAntibodyMap();
		
		//checkPorts();
	}
	
	// process ports
	/**
	 * Initialize the inputLocation lists. (using hash set to avoid )
	 * For cells, its a list of point objcet containing grid locations in distance 1 to port exits;
	 * for virus it's the list of locations at port exits.
	 */
	public void initializeInportLocations(){
		InputPort ip;
		Iterator<FixedObject> ipIter = inputPorts.iterator();
		while(ipIter.hasNext()){
			ip = (InputPort)ipIter.next();
			if(ip.isTCellEntry()){
				inputLocationTCell.addAll(cellSpace.getOpenNeighborLocations(ip.getX(), ip.getY(), 1));
			}
			if(ip.isBCellEntry()){
				inputLocationBCell.addAll(cellSpace.getOpenNeighborLocations(ip.getX(), ip.getY(), 1));
			}
			if(ip.isAPCEntry()){
				inputLocationAPCell.addAll(cellSpace.getOpenNeighborLocations(ip.getX(), ip.getY(), 1));
			}
			if(ip.isVirusEntry()){
				inputLocationVirus.add(new Point(ip.getX(), ip.getY()));
			}
		}
	}
	//recruitment
	
	public void recruitAgents(){
		processInputCellList(recruitCellList);
		recruitCellList.clear();
		processInputCellList(inputCellsList);
		returnStuckCells(inputCellsList, theModel.bloodStream.inputCellsList);
		prcoessInputVirusList();
	}
	
	public void processInputCellList(ArrayList<Cell> list){
		HashSet<Point> tCellLocation = getOpenLocations(inputLocationTCell);
		HashSet<Point> bCellLocation = getOpenLocations(inputLocationBCell);
		HashSet<Point> apCellLocation = getOpenLocations(inputLocationAPCell);
		HashSet<Point> usedLocations = new HashSet<Point>();
		//iterate through and put 
		Cell c;
		Point pt = null;
		boolean found = false;
		Iterator<Cell> cIter = list.iterator();
		Iterator<Point> ptItert = tCellLocation.iterator();
		Iterator<Point> ptIterb = bCellLocation.iterator();
		Iterator<Point> ptItera = apCellLocation.iterator();
		while(cIter.hasNext()){
			c = cIter.next();
			if (TCell.class.isInstance(c)){
				while(ptItert.hasNext()){
					pt = ptItert.next();
					if(usedLocations.contains(pt)){
						continue;
					}else {
						found = true;
						break;	
					}
				}
			}else if (BCell.class.isInstance(c)){
				while(ptIterb.hasNext()){
					pt = ptIterb.next();
					if(usedLocations.contains(pt)){
						continue;
					}else {
						found = true;
					break;	
					}
				}
			}else if (APCell.class.isInstance(c)){
				while(ptItera.hasNext()){
					pt = ptItera.next();
					if(usedLocations.contains(pt)){
						continue;
					}else {
						found = true;
					break;	
					}
				}
			}
			if(found && pt != null){
//				if(APCell.class.isInstance(c)){
//					System.out.println("DC recruit to organ");
//				}
				cellSpace.addObjectAt((int)pt.getX(), (int)pt.getY(), c);
				cellList.add(c);
				c.updateMyList(cellList);
				cIter.remove();
				usedLocations.add(pt);
				found = false;
				pt = null;
			}
			
		}
	}
	/**
	 * pick open locations from the list and return the hash set
	 * @param inHash (constant inputLocation hash)
	 * @return openLocations
	 */
	public HashSet<Point> getOpenLocations(HashSet<Point> inHash) {
		ArrayList<Point> oList = new ArrayList<Point>();
		Point pt;
		Iterator<Point> ptIter = inHash.iterator();
		while (ptIter.hasNext()) {
			pt = ptIter.next();
			if(cellSpace.getObjectAt((int)pt.getX(),(int)pt.getY()) == null) {
				oList.add(pt);
			}
		}
		SimUtilities.shuffle( oList, uchicago.src.sim.util.Random.uniform );
		HashSet<Point> oHash = new HashSet<Point>();
		oHash.addAll(oList);
		return oHash;
	}
	
	public void returnStuckCells(ArrayList<Cell> hs, ArrayList<Cell> hd){
		hd.addAll(hs);
		Iterator<Cell> cIter = hs.iterator();
		Cell c;
		while (cIter.hasNext()) {
			c = cIter.next();
			c.resetOrgan(theModel.bloodStream);
		}
		hs.clear();
		
	}
	
	public void prcoessInputVirusList(){
		int portNr = inputLocationVirus.size();
		if(portNr == 0) {
			return;
		}
		SimUtilities.shuffle( inputLocationVirus, uchicago.src.sim.util.Random.uniform );
		Virus v;
		Point pt;
		int counter = 0;
		
		Iterator<Virus> vIter = inputVirusList.iterator();
		while(vIter.hasNext()){
			v = vIter.next();
			pt = inputLocationVirus.get(counter % portNr);
			virusSpace.addObjectAt((int)pt.getX(), (int)pt.getY(), v);
			virusList.add(v);
			v.updateMyList(virusList);
			vIter.remove();
			++counter;
		}
	}
	// add antibody
	public void addAntibodyAt(int x, int y, double a) {
		double v = a + antibodySpace.getValueAt(x, y);
		antibodySpace.putValueAt(x, y, v);
	}
	
	
	
	/**
	 * update values in virusAntibodyMap
	 */
	public void updateVirusAntibodyMap() {
		
		int virusLoad, antibodyLoad, newValue;
		//double totalAntibody = 0;
		for(int i = 0; i< xSize; ++i){
			for (int j = 0; j < ySize; ++j) {
				HashSet<Virus> list =  (HashSet<Virus>)virusSpace.getObjectAt(i,j);
				//process transformaiton first 3 digit for virus, last 3 for antibody
				virusLoad = processLoad(list.size(), virusDisplayUpperLimit);
				antibodyLoad = processLoad(antibodySpace.getValueAt(i, j), AntibodyDisplayUpperLimit);
				//totalAntibody += antibodySpace.getValueAt(i, j);
				newValue = virusLoad * theModel.colorMapSize + antibodyLoad;
//				if(antibodyLoad != 0){
//					System.out.printf("(%d,%d): antibody: %d, newvalue: %d\n", i, j, antibodyLoad, newValue);
//				}
				//update value
				virusAntibodyMap.putValueAt(i, j, (double)newValue);
				//System.out.println(newValue);
			}
		}
		//System.out.println(totalAntibody);
	}
	
	public int processLoad(double v, int limit) {
		if( v > limit) {
			v = limit;
		}
		return (int)(v*(theModel.colorMapSize-1)/limit);
	}
	
	/**
	 * return the total value of antibodies
	 * @return
	 */
	public double getTotalAntibodyLoad(){
		double total = 0;
		for(int i = 0; i< xSize; ++i){
			for (int j = 0; j < ySize; ++j) {
				total += antibodySpace.getValueAt(i, j);
			}
		}
		return total;
	}
	////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	////// methods for verification 
	//////
	/**
	 * see if neighbors are as designed
	 */
	public void checkNeighbors() {
		Iterator<Cell> cIter = cellList.iterator();
		while (cIter.hasNext()) {
			Cell c = cIter.next();
			System.out.println("celltype: " + c.toString());
			ArrayList<Object> neighbor = this.cellSpace.getMooreNeighborOfType(c.getX(), c.getY(), 1, TCell.class);
			System.out.println("Tcell neighbor 1: " + neighbor.size());
			neighbor = this.cellSpace.getMooreNeighborOfType(c.getX(), c.getY(), 1, OutputPort.class);
			System.out.println("Out port neighbor 1: " + neighbor.size());
			neighbor = this.cellSpace.getMooreNeighborOfType(c.getX(), c.getY(), 2, OutputPort.class);
			System.out.println("Out port neighbor 2: " + neighbor.size());
		}
	}
	
	/**
	 * check if the Ports are created in the wanted way
	 */
	public void checkPorts() {
		CD4TCell a1 = new CD4TCell();
		CD8TCell a2 = new CD8TCell();
		BCell a3 = new BCell();
		APCell a4 = new APCell();
		Virus a5 = new Virus();
		FixedObject a6 = new FixedObject();
		
		Iterator<FixedObject> pIter = allPorts.iterator();
		while (pIter.hasNext()) {
			FixedObject p = pIter.next();
			System.out.println("\n"+p.toString());
			System.out.println(p.Organ2D.toString());
			
			if (p.getClass() == PortEntry.class) {
				PortEntry p1 = (PortEntry)p;
				System.out.println(p1.isEntry(a1));
				System.out.println(p1.isEntry(a2));
				System.out.println(p1.isEntry(a3));
				System.out.println(p1.isEntry(a4));
				System.out.println(p1.isEntry(a5));
				System.out.println(p1.isEntry(a6));
			}else if (p.getClass() == PortExit.class) {
				PortExit p1 = (PortExit)p;
				System.out.println(p1.isExit(a1));
				System.out.println(p1.isExit(a2));
				System.out.println(p1.isExit(a3));
				System.out.println(p1.isExit(a4));
				System.out.println(p1.isExit(a5));
				System.out.println(p1.isExit(a6));
			}else {
				PortDual p1 = (PortDual)p;
				System.out.println(p1.isEntry(a1));
				System.out.println(p1.isEntry(a2));
				System.out.println(p1.isEntry(a3));
				System.out.println(p1.isEntry(a4));
				System.out.println(p1.isEntry(a5));
				System.out.println(p1.isEntry(a6));
				System.out.println(p1.isExit(a1));
				System.out.println(p1.isExit(a2));
				System.out.println(p1.isExit(a3));
				System.out.println(p1.isExit(a4));
				System.out.println(p1.isExit(a5));
				System.out.println(p1.isExit(a6));
			}
		}
	}
}
